/*
 *  Copyright © 2016-2018, Turing Technologies, an unincorporated organisation of Wynne Plaga
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package com.turingtechnologies.materialscrollbardemo;

import ohos.agp.colors.RgbColor;
import ohos.agp.components.*;
import ohos.agp.components.element.ShapeElement;
import ohos.agp.render.Canvas;

import ohos.agp.text.Font;
import ohos.agp.utils.Color;
import ohos.agp.utils.TextTool;
import ohos.app.Context;
import ohos.app.dispatcher.task.Revocable;
import ohos.multimodalinput.event.MmiPoint;
import ohos.multimodalinput.event.TouchEvent;

public class MaterialScrollBar extends DirectionalLayout {
    private static final int STYLE_NONE = -1;
    private final ListContainerScrolledListener scrollListener = new ListContainerScrolledListener(this);
    private ListContainer listContainer;
    private Component bubble;
    private Component handle;
    private Component handleCom;
    private Text bubbleTextView;
    private int bubbleOffset;
    private Color handleColor;
    private Color handleComColor;
    private Color bubbleColor;
    private Color bubbleTextColor;
    private int bubbleTextSize;
    private String textStyle;
    private int scrolledOrientation;
    private int totalScrollH = 0;
    private int maxVisibility;
    private boolean isChangingPosition;
    private ScrollerViewProvider viewProvider;
    private TitleInterFace titleProvider;
    private float offset = 0;
    private DependentLayout dependentLayout;
    private int mtag;
    private int mtitleTag;
    private int targetPos;
    private Revocable revocable;

    /**
     * build
     *
     * @param context context
     */
    public MaterialScrollBar(Context context) {
        super(context, null);
    }

    /**
     * 构造方法
     *
     * @param context context
     * @param attrSet attrSet
     */
    public MaterialScrollBar(Context context, AttrSet attrSet) {
        this(context, attrSet, null);
    }

    /**
     * 构造方法
     *
     * @param context context
     * @param attrSet attrSet
     * @param styleName styleName
     */
    public MaterialScrollBar(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
        setClipEnabled(false);
        initAttrSet(attrSet);
        initArrange();
    }

    /**
     * 初始化属性值
     *
     * @param attrSet attrSet
     */
    private void initAttrSet(AttrSet attrSet) {
        handleColor = AttrValue.get(attrSet, "handleColor", new Color(STYLE_NONE));
        handleComColor = AttrValue.get(attrSet, "handlecomColor", new Color(STYLE_NONE));
        bubbleColor = AttrValue.get(attrSet, "bubbleColor", new Color(STYLE_NONE));
        bubbleTextColor = AttrValue.get(attrSet, "bubbleTextColor", new Color(STYLE_NONE));
        bubbleTextSize = AttrValue.getDimension(attrSet, "bubbleTextSize", STYLE_NONE);
        textStyle = AttrValue.get(attrSet, "textStyle", "");
        maxVisibility = getVisibility();
        setOrientation(getOrientation());
        setViewProvider(new Indicator());
    }

    /**
     * Enables custom layout for {@link MaterialScrollBar}.
     *
     * @param aViewProvider A {@link ScrollerViewProvider} for the {@link MaterialScrollBar} to use when building layout.
     */
    public void setViewProvider(ScrollerViewProvider aViewProvider) {
        removeAllComponents();
        this.viewProvider = aViewProvider;
        viewProvider.setFastScroller(this);
        bubble = viewProvider.provideBubbleView(this);
        handle = viewProvider.provideHandleView(this);
        handleCom = viewProvider.provideHandleComte(this);
        bubbleTextView = viewProvider.provideBubbleTextView();
        dependentLayout = new DependentLayout(getContext());
        dependentLayout.addComponent(handleCom);
        dependentLayout.addComponent(handle);
        addComponent(bubble);
        addComponent(dependentLayout);
        initDrag();
    }

    /**
     * 设置拖拽监听
     */
    private void initDrag() {
        handle.setDraggedListener(Component.DRAG_HORIZONTAL_VERTICAL, new DraggedListener() {
            @Override
            public void onDragDown(Component component, DragInfo dragInfo) {
            }

            @Override
            public void onDragStart(Component component, DragInfo dragInfo) {
            }

            @Override
            public void onDragUpdate(Component component, DragInfo dragInfo) {
            }

            @Override
            public void onDragEnd(Component component, DragInfo dragInfo) {
            }

            @Override
            public void onDragCancel(Component component, DragInfo dragInfo) {
            }

            @Override
            public boolean onDragPreAccept(Component component, int dragDirection) {
                return true;
            }
        });
    }

    /**
     * setListContainer
     *
     * @param aListContainer aListContainer
     * @param tag tag
     * @param titleTag titleTag
     */
    public void setListContainer(ListContainer aListContainer, int tag, int titleTag) {
        mtag = tag;
        mtitleTag = titleTag;
        if (tag == 1) {
            this.listContainer = aListContainer;
            if (listContainer.getItemProvider() instanceof TitleInterFace) {
                titleProvider = (TitleInterFace) listContainer.getItemProvider();
            }
            listContainer.setScrolledListener(scrollListener);
            invalidateVisibility();
            listContainer.setLayoutRefreshedListener(new LayoutRefreshedListener() {
                @Override
                public void onRefreshed(Component component) {
                    Component cp = listContainer.getComponentAt(0);
                    if (cp != null && totalScrollH == 0) {
                        int height = cp.getHeight();
                        int count = listContainer.getItemProvider().getCount();
                        totalScrollH = height * count;
                    }
                }
            });
            listContainer.addItemVisibilityChangedListener(new ListContainer.ItemVisibilityChangedListener() {
                @Override
                public void onItemAdded(Component component, int i) {
                    invalidateVisibility();
                }

                @Override
                public void onItemRemoved(Component component, int i) {
                    invalidateVisibility();
                }
            });
        } else if (tag == 2) {
            dependentLayout.setVisibility(HIDE);
            this.listContainer = aListContainer;
            if (listContainer.getItemProvider() instanceof TitleInterFace) {
                titleProvider = (TitleInterFace) listContainer.getItemProvider();
            }
            listContainer.setScrolledListener(scrollListener);
            invalidateVisibility();
            listContainer.setLayoutRefreshedListener(new LayoutRefreshedListener() {
                @Override
                public void onRefreshed(Component component) {
                    Component cp = listContainer.getComponentAt(0);
                    if (totalScrollH == 0 && cp != null) {
                        int height = cp.getHeight();
                        totalScrollH = height * listContainer.getItemProvider().getCount();
                    }
                }
            });

            listContainer.addScrolledListener(new ScrolledListener() {
                @Override
                public void onContentScrolled(Component component, int i, int i1, int i2, int i3) {
                    dependentLayout.setVisibility(VISIBLE);
                    scrollMax();
                }
            });
            listContainer.setScrollListener(new ListContainer.ScrollListener() {
                @Override
                public void onScrollFinished() {
                    initTime();
                }
            });
            listContainer.addItemVisibilityChangedListener(new ListContainer.ItemVisibilityChangedListener() {
                @Override
                public void onItemAdded(Component component, int i) {
                    invalidateVisibility();
                }

                @Override
                public void onItemRemoved(Component component, int i) {
                    invalidateVisibility();
                }
            });
            listContainer.setTouchEventListener(new TouchEventListener() {
                @Override
                public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
                    int action = touchEvent.getAction();
                    if (action == TouchEvent.PRIMARY_POINT_DOWN) {
                        initTime();
                    }
                    return true;
                }
            });
        }
    }

    private void initTime() {
        if (revocable != null) {
            revocable.revoke();
        }
        revocable = getContext().getUITaskDispatcher().delayDispatch(new Runnable() {
            @Override
            public void run() {
                dependentLayout.setVisibility(Component.HIDE);
            }
        }, 3000);
    }

    private void initArrange() {
        addDrawTask(new DrawTask() {
            @Override
            public void onDraw(Component component, Canvas canvas) {
                bubbleOffset = viewProvider.getBubbleOffset();
                applyStyling();
            }
        });

        setLayoutRefreshedListener(new LayoutRefreshedListener() {
            @Override
            public void onRefreshed(Component component) {
                invalidateVisibility();
                if (mtag == 1) {
                    initHandleMovement();
                } else if (mtag == 2) {
                    initDateHandleMovement();
                }
                bubbleOffset = viewProvider.getBubbleOffset();
            }
        });
    }

    /**
     * 获取可滑动的总高度
     *
     * @return 高度总滑动值
     */
    protected int getTotalScrollH() {
        return totalScrollH;
    }

    private void initHandleMovement() {
        handle.setTouchEventListener(new TouchEventListener() {
            @Override
            public boolean onTouchEvent(Component component, TouchEvent event) {
                MmiPoint mScreenPosition = event.getPointerScreenPosition(0);
                switch (event.getAction()) {
                    case TouchEvent.PRIMARY_POINT_DOWN:
                        scrollMax();
                        if (titleProvider != null) {
                            viewProvider.onHandleGrabbed();
                            float offsetY = mScreenPosition.getY() - handle.getLocationOnScreen()[1];
                            offset = offsetY;
                            return true;
                        }
                        break;
                    case TouchEvent.POINT_MOVE:
                        scrollMax();
                        isChangingPosition = true;
                        float relativePos = getRelativeTouchPosition(event);
                        if (relativePos > 1) {
                            relativePos = 1;
                        }
                        else if (relativePos < 0) {
                            relativePos = 0;
                        }
                        setScrolledPosition(relativePos);
                        setRecyclerViewPosition(relativePos);
                        break;
                    case TouchEvent.PRIMARY_POINT_UP:
                    case TouchEvent.CANCEL:
                        scrollMin();
                        isChangingPosition = false;
                        if (titleProvider != null) {
                            viewProvider.onHandleReleased();
                        }
                        break;
                    default:
                        break;
                }
                return false;
            }
        });
    }

    private void initDateHandleMovement() {
        handle.setTouchEventListener(new TouchEventListener() {
            @Override
            public boolean onTouchEvent(Component component, TouchEvent event) {
                MmiPoint mScreenPosition = event.getPointerScreenPosition(0);
                switch (event.getAction()) {
                    case TouchEvent.PRIMARY_POINT_DOWN:
                        dependentLayout.setVisibility(VISIBLE);
                        scrollMax();
                        if (titleProvider != null) {
                            viewProvider.onHandleGrabbed();
                            float offsetY = mScreenPosition.getY() - handle.getLocationOnScreen()[1];
                            offset = offsetY;
                            return true;
                        }
                        break;
                    case TouchEvent.POINT_MOVE:
                        dependentLayout.setVisibility(VISIBLE);
                        scrollMax();
                        isChangingPosition = true;
                        float relativePos = getRelativeTouchPosition(event);
                        if (relativePos > 1) {
                            relativePos = 1;
                        }
                        else if (relativePos < 0) {
                            relativePos = 0;
                        }
                        setScrolledPosition(relativePos);
                        setRecyclerViewPosition(relativePos);
                        break;
                    case TouchEvent.PRIMARY_POINT_UP:
                    case TouchEvent.CANCEL:
                        initTime();
                        isChangingPosition = false;
                        if (titleProvider != null) {
                            viewProvider.onHandleReleased();
                        }
                        break;
                    default:
                        break;
                }
                return true;
            }
        });
    }

    private void scrollMin() {
        ComponentContainer.LayoutConfig layoutConfig = new ComponentContainer.LayoutConfig(40, ComponentContainer.LayoutConfig.MATCH_PARENT);
        handleCom.setLayoutConfig(layoutConfig);
        handleCom.setMarginLeft(30);

        if (handle instanceof Handle) {
            ((Handle) handle).drawMin();
        }
    }

    private void scrollMax() {
        ComponentContainer.LayoutConfig layoutConfig = new ComponentContainer.LayoutConfig(60, ComponentContainer.LayoutConfig.MATCH_PARENT);
        handleCom.setLayoutConfig(layoutConfig);
        handleCom.setMarginLeft(10);

        if (handle instanceof Handle) {
            ((Handle) handle).drawMax();
        }
    }

    private float getRelativeTouchPosition(TouchEvent event) {
        float aParent = event.getPointerScreenPosition(0).getY() - ScrollingUtilities.getViewRawY(handle) - offset;
        return aParent / (getHeight() - handle.getHeight());
    }

    private void setRecyclerViewPosition(float relativePos) {
        if (listContainer == null) {
            return;
        }
        if (mtitleTag == 0) {
            listContainer.scrollTo(0, (int) (relativePos * (getTotalScrollH() / 5 + 30)));
        } else if (mtitleTag == 1) {
            listContainer.scrollTo(0, (int) (relativePos * (getTotalScrollH() / 5 + 30)));
        }
        setTextData(relativePos);
        scrollListener.notifyListeners(relativePos);
    }

    void setScrolledPosition(float relativePos) {
        float result = relativePos* ((float) getHeight() - (float) handle.getHeight()) + 6 * (float) bubbleOffset / 2 + 70;
        bubble.setTranslationY(ScrollingUtilities.getValueInRange(0,
                getHeight() - bubble.getHeight(), result) - bubble.getTop() + 20);
        handle.setTranslationY(ScrollingUtilities.getValueInRange(
                0,
                getHeight() - handle.getHeight(),
                (relativePos * (getHeight() - handle.getHeight() + 30))) - handle.getTop()
        );
        setTextData(relativePos);
    }

    private void setTextData(double relativePos) {
        int itemCount = listContainer.getItemProvider().getCount();
        targetPos = (int) ScrollingUtilities.getValueInRange(0, itemCount - 1, (int) (relativePos * (float) itemCount));
        if (titleProvider != null && bubbleTextView != null) {
            if (mtitleTag == 0) {
                bubbleTextView.setText(titleProvider.getSectionTitle(targetPos));
            } else if (mtitleTag == 1) {
                bubbleTextView.setText(titleProvider.getSectionTitle(listContainer.getFirstVisibleItemPosition()));
                int position = listContainer.getFirstVisibleItemPosition();
                Component component = listContainer.getComponentAt(position);
                if (component.getPivotY() > 0 && component.getPivotY() < 1) {
                    bubbleTextView.setText(titleProvider.getSectionTitle(listContainer.getFirstVisibleItemPosition() + 1));
                } else if (component.getPivotY() > 1) {
                    bubbleTextView.setText(titleProvider.getSectionTitle(listContainer.getFirstVisibleItemPosition()));
                }
            }
        }
    }

    private void invalidateVisibility() {
        if (listContainer.getItemProvider() == null
                || listContainer.getItemProvider().getCount() == 0
                || isRecyclerViewNotScrollable()
                || maxVisibility != Component.VISIBLE
        ) {
            super.setVisibility(INVISIBLE);
        } else {
            super.setVisibility(VISIBLE);
        }
    }

    private boolean isRecyclerViewNotScrollable() {
        return listContainer.getHeight() >= totalScrollH;
    }

    private void applyStyling() {
        if (bubbleColor.getValue() != STYLE_NONE) {
            setBackgroundTint(bubbleTextView, bubbleColor);
        }
        if (handleColor.getValue() != STYLE_NONE) {
            setBackgroundTint(handle, handleColor);
        }
        if (handleComColor.getValue() != STYLE_NONE) {
            setBackgroundTint(handleCom, handleComColor);
        }
        if (bubbleTextColor.getValue() != STYLE_NONE) {
            bubbleTextView.setTextColor(bubbleTextColor);
        }
        if (bubbleTextSize != STYLE_NONE) {
            bubbleTextView.setTextSize(bubbleTextSize);
        }
        if (!TextTool.isNullOrEmpty(textStyle)) {
            Font font = null;
            if (TextTool.isEqual("bold", textStyle)) {
                font = new Font.Builder("").setWeight(Font.BOLD).build();
            } else if (TextTool.isEqual("italic", textStyle)) {
                font = new Font.Builder("").makeItalic(true).build();
            }
            if (font != null) {
                bubbleTextView.setFont(font);
            }
        }
    }

    private void setBackgroundTint(Component view, Color color) {
        final ShapeElement background = view.getBackgroundElement()
                instanceof ShapeElement ? (ShapeElement) view.getBackgroundElement() : null;
        if (background == null) {
            return;
        }
        background.setRgbColor(RgbColor.fromArgbInt(color.getValue()));
        ScrollingUtilities.setBackground(view, background);
    }

    @Override
    public void setVisibility(int visibility) {
        maxVisibility = visibility;
        invalidateVisibility();
    }

    @Override
    public void setOrientation(int orientation) {
        scrolledOrientation = orientation;
        super.setOrientation(orientation == HORIZONTAL ? VERTICAL : HORIZONTAL);
    }

    /**
     * 设置文字样式
     *
     * @param style 样式
     */
    public void setTextStyle(String style) {
        this.textStyle = style;
        invalidate();
    }

    public boolean isVertical() {
        return scrolledOrientation == VERTICAL;
    }

    boolean shouldUpdateHandlePosition() {
        return handle != null && !isChangingPosition && listContainer.getChildCount() > 0;
    }

    ScrollerViewProvider getViewProvider() {
        return viewProvider;
    }
}
